package recite.word.client.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import recite.word.client.dto.plan.PlanImportWordDTO;
import recite.word.client.dto.plan.PlanSetPlanDTO;
import recite.word.client.service.*;
import recite.word.client.vo.plan.CalendarData;
import recite.word.client.vo.plan.DayCompleteVO;
import recite.word.client.vo.plan.PlanGetNowVO;
import recite.word.mybatis.auto.dao.UserMapper;
import recite.word.mybatis.auto.model.*;
import recite.word.service.base.common.enums.DeleteEnum;
import recite.word.service.base.common.request.BaseRequest;
import recite.word.service.base.common.threadLocal.UserContent;
import recite.word.service.base.util.common.Asserts;
import recite.word.service.base.util.date.DateFormat;
import recite.word.service.base.util.transaction.TransactionUtil;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @program: recite-word-back
 * @description:
 * @author: Mr.lin
 * @create: 2023-01-27 14:38
 **/

@Service
public class PlanServiceImpl implements IPlanService {

	@Resource
	private UserMapper userMapper;
	@Autowired
	private IBookService bookService;
	@Autowired
	private IUserWordService userWordService;
	@Autowired
	private IUniqueWordService uniqueWordService;
	@Autowired
	private ICalendarService calendarService;
	@Autowired
	private TransactionUtil transactionUtil;
	
	@Override
	public PlanGetNowVO getNow() {
		Long userId = UserContent.get();
		User user = userMapper.selectById(userId);
		PlanGetNowVO ans = new PlanGetNowVO();
		if (user.getPlanBookId() == null) {
			ans.setHavePlan(false);
			return ans;
		} else {
			ans.setHavePlan(true);
		}
		Long bookId = user.getPlanBookId();
		ans.setPlanBookId(bookId);
		ans.setDayCompleteNum(user.getDayCompleteNum());
		ans.setEveryDayWordNum(user.getEverydayWordNum());
		Book book = bookService.getById(bookId);
		ans.setBookName(book.getName());
		// 已学单词数
		int haveStudyNum = userWordService.count(Wrappers.<UserWord>lambdaQuery()
				.eq(UserWord::getBookId, bookId)
				.eq(UserWord::getUserId, userId)
				.eq(UserWord::getIsRecite,true)
				.eq(UserWord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
		// 剩余天数 = (总单词数量 - 已学单词数) / 每日学习单词数
		int remainDay = (int)Math.ceil((double) (book.getVocabulary() - haveStudyNum) / user.getEverydayWordNum());
		Calendar instance = Calendar.getInstance();
		instance.add(Calendar.DATE,remainDay);
		ans.setExpectTime(instance.getTime());
		return ans;
	}

	/**
	 * 设置计划
	 * @param dto
	 */
	@Override
	public void setPlan(BaseRequest<PlanSetPlanDTO> dto) {
		PlanSetPlanDTO data = dto.getData();
		Asserts.isNotNull(data.getBookId(),"书籍不能为空");
		Asserts.isNotNull(data.getDayCompleteNum(),"完成天数不能为空");
		Asserts.isNotNull(data.getEveryDayWordNum(),"每日单词数不能为空");
		int b = userMapper.update(null,Wrappers.<User>lambdaUpdate()
				.eq(User::getId, UserContent.get())
				.set(User::getPlanBookId, data.getBookId())
				.set(User::getDayCompleteNum, data.getDayCompleteNum())
				.set(User::getEverydayWordNum, data.getEveryDayWordNum()));
		Asserts.isTrue(b == 1,"设置计划失败");
	}

	/**
	 * 查询是否有计划
	 * @return
	 */
	@Override
	public Boolean havePlan() {
		User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
				.eq(User::getId, UserContent.get())
				.eq(User::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
		return user.getPlanBookId() != null;
	}

	/**
	 * 导入新书单词
	 * @param dto
	 */
	@Override
	public void importWord(BaseRequest<PlanImportWordDTO> dto) {
		PlanImportWordDTO data = dto.getData();
		Asserts.isNotNull(data.getBookId(),"单词书id为空");
		Long userId = UserContent.get();
		// user_word表中改用户单词数据为0时导入新书
		int count = userWordService.count(new LambdaQueryWrapper<UserWord>()
				.eq(UserWord::getUserId, userId)
				.eq(UserWord::getBookId, data.getBookId())
				.eq(UserWord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
		if (count == 0) {
			List<UniqueWord> uniqueWords = uniqueWordService.list(Wrappers.<UniqueWord>lambdaQuery()
					.eq(UniqueWord::getBookId, data.getBookId()).eq(UniqueWord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
			List<UserWord> list = new ArrayList<>();
			for (UniqueWord uniqueWord : uniqueWords) {
				UserWord userWord = filedCopy(uniqueWord);
				userWord.setId(null);
				userWord.setUserId(userId);
				list.add(userWord);
			}
			boolean b = userWordService.saveBatch(list);
			Asserts.isTrue(b,"导入新书失败");
		}
	}

	/**
	 * 设置今日计划完成并返回数据
	 * @return
	 */
	@Override
	public DayCompleteVO setDayComplete() {
		Long userId = UserContent.get();
		User user = userMapper.selectById(userId);
		Asserts.isNotNull(user,"用户不存在");
		// 去更新记录, 1.日历记录表中添加今日记录 2.用户表中学习单词数添加,坚持天数+1
		String nowDay = DateFormat.format(new Date());
		String nowMonth = DateFormat.format2month(new Date());
		CalendarRecord toDayCalendar = calendarService.getOne(new LambdaQueryWrapper<CalendarRecord>()
				.eq(CalendarRecord::getCompleteDate, nowDay)
				.eq(CalendarRecord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
		// 今日已背单词数
		int dayReciteCount = getTodayStudyNum();
		// 今日还未打开就先添加打卡记录
		if (ObjectUtil.isNull(toDayCalendar)) {
			// 日历表添加记录
			CalendarRecord calendarRecord = new CalendarRecord();
			calendarRecord.setCompleteDate(nowDay);
			calendarRecord.setCompleteNum(dayReciteCount);
			calendarRecord.setIsComplete(Byte.valueOf("1"));
			calendarRecord.setCompleteDateMonth(nowMonth);
			calendarRecord.setUserId(userId);
			boolean b = transactionUtil.transactional(() -> {
				boolean b1 = calendarService.save(calendarRecord);
				// 用户表中学习单词数添加,坚持天数+1
				int b2 = userMapper.update(null, new LambdaUpdateWrapper<User>()
						.eq(User::getId, userId)
						.set(User::getStudyWordNum, user.getStudyWordNum() + dayReciteCount)
						.set(User::getInsistDayNum, user.getInsistDayNum() + 1));
				return b1 && b2 == 1;
			});
			Asserts.isTrue(b, "更新数据失败");
		}

		// 查询数据
		// 连续签到天数
		LambdaQueryWrapper<CalendarRecord> wrapper = new LambdaQueryWrapper<CalendarRecord>()
				.select(CalendarRecord::getCompleteDate,CalendarRecord::getCreatedTime)
				.eq(CalendarRecord::getUserId, userId)
				.eq(CalendarRecord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode())
				.orderByDesc(CalendarRecord::getCreatedTime);
		List<CalendarRecord> calendarDay = calendarService.list(wrapper);
		int continuesDay = 0;
		Calendar toDay = Calendar.getInstance();
		for (CalendarRecord record : calendarDay) {
			if (!DateFormat.format(toDay.getTime()).equals(record.getCompleteDate())) break;
			toDay.add(Calendar.DATE,-1);
			continuesDay ++;
		}
		// 当月完成日期
		wrapper.eq(CalendarRecord::getCompleteDateMonth,nowMonth);
		List<CalendarData> dayList = calendarService.list(wrapper).stream().map(item -> new CalendarData(item.getCompleteDate())).collect(Collectors.toList());
		// 返回值
		DayCompleteVO ans = new DayCompleteVO();
		ans.setStudyNum(dayReciteCount);
		ans.setPersistNum(user.getInsistDayNum() + 1);
		ans.setAccumulateDay(user.getInsistDayNum() + 1);
		ans.setDayList(dayList);
		ans.setContinuesDay(continuesDay);
		return ans;
	}

	/**
	 * 获取今日学习单词数量
	 */
	@Override
	public Integer getTodayStudyNum(){
		String nowDay = DateFormat.format(new Date());
		// 今日已背单词数
		return userWordService.count(new LambdaQueryWrapper<UserWord>().eq(UserWord::getUserId, UserContent.get())
				.eq(UserWord::getIsRecite, true)
				.eq(UserWord::getReciteDate, nowDay)
				.eq(UserWord::getDeleteStatus, DeleteEnum.NOT_DELETED.getCode()));
	}

	public static UserWord filedCopy(UniqueWord uniqueWord)  {
		Class<? extends UniqueWord> uniqueWordClass = uniqueWord.getClass();
		Field[] uniqueFields = uniqueWordClass.getDeclaredFields();
		Map map = new HashMap();
		try {
			for (Field field : uniqueFields) {
				field.setAccessible(true); // 允许这个private可访问
				Object o = field.get(uniqueWord);
				String fieldName = field.getName();
				map.put(fieldName,o);
			}
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return JSONUtil.toBean(JSONUtil.toJsonStr(map), UserWord.class);
	}

}
